'''
Created on Sep 21, 2010

@author: samantha
'''
import types
import GSN.exceptions as ex
import GSN.database as db
import OpenBayes as bayes
import uuid


def validateProbability (p):
    if (p < 0) or (p > 1):
        raise ex.InvalidProbability()

#A graph is the logical unit of bayesian inference for a given delimited domain. A collection of nodes and edges.  
class Graph(db.PersistentObject):
    __table__ = "Graph"
    __attributes__ = ["name"]
    def __init__(self, name):
        self.name = name
        self.nodes = set([])
        self.ob_graph = bayes.Graph

    def handleFetch(self):
        '''get my nodes'''
        pass
    
    def addNode(self,node):
        '''add a node to the graph. also add any known parents'''
        self.nodes.add(node)
        for parent in node.parents:
            self.addNode(parent)
        node.setGraph(self)
        
    
class Node (db.PersistentObject):
    __table__ = "Node"
    __attributes__ = ["name", "shortDescription", "url", "graphId", "type"]
    

    def __init__(self, name, shortDescription = None, url = None, type = "plain"):
        db.PersistentObject.__init__(self)
        if not (shortDescription or url):
            raise "Node creation requires either short description on a url"
        self.name = name
        self.shortDescription = shortDescription
        self.url = url
        if type(graphId) == types.IntType:
            graphOid = int_uuid(graphId)
        else:
            graphOid = graphId
        if graphOid:
            self.setGraph(Graph.findById(graphOid))
        self.priorProbality = 0.0
        self.parents = set([])
        self.cpTable = [] #the conditional probabilities
        self.parentOrder = {}

    def setGraph(self, grapb):
        if graph:
            self.graph = graph
            self.graphId = graph.oid
        
    def handleFetch(self):
        '''I need to grab my collections'''
        fetchParents = "select parentId, parentNum from Edge where childId = %s order by parentNum" % (self.oid.int)
        fetchCPTable = "select combination, truth from Conditional where owner = %s" % (self.oid.int)
        Tranaction.current().executeFetch(fetchParents)
        
    def children(self):
        relevantEdges = Edge.find(parent=self.oid)
        

    def isCircularParent(self, possibleParent):
        '''is circular if the parent is myself or one of my children'''
        return (self == possibleParent) or possibleParent.parentClosue().__contains__(self)
    
    def parentClosure(self):
        '''returns the my ancestors (parents and their parents..)'''
        parents = self.parents
        for parent in parents:
            parents.union(parent.parentClosure())
        return parents
    
    def addParent(self, parent):
        '''add a parent and update everything needed for this.  Note that the
        conditional probabilities will be added defaulting to assuming this parent
        makes no real difference. The actual probabilities need to be adjusted. Mark
        this as being the case'''
        if self.parents.__contains__(parent):
            return
        #validate parent
        if type(parent) == types.StringType:
            temp = parent
            parent = self.find(name = parent)
            if not parent:
                raise ex.UnknownNodeException(temp)    
        if self.isCircularParent(parent):
            raise RuntimeError("Making %s a parent of %s would be circular" % (parent.name, self.name))
        self.parentOrder[parent] = len(self.parents)
        self.parents.add(parent)
        
    def setProbTrue (self, value, *parentValues):
        '''Since all nodes are binary we only need to set the probability(self=True) for
        each combination of parents.
        value - probability of being Ture
        *parentValues - sequence of pairs of parent node and truth value. If no pairs then this only sets the prior probability of this node.  '''
        validateProbability(value)
        if parentValues: #set one row of my conditional probabilities
            pv = self.parentVector(*parentValues)
            self.cpTable[pv] = float(value)
        else: #just set my own prior or only probability of being true
            self.probTrue = float(value)
        
    def parentVector(self, *parentValues):
        '''converts a sequence of node,truthValue tuples to an integer by using the
        order of the parents to set the corresponding bits'''
        vector = 0
        for parent,value in parentValues:
            if not self.parents.has_key(parent):
                raise RuntimeError("%s is not a parent" % parent.name)
            if value:
                vector = vector | 1 << self.parentOrder[parent]
        return vector

    def truthVector(self, parentVector):
        ''' inverse of parentVector method returning a tuple of true/false values corresponding to the binary bits of the parentVector '''
        return [(parentVector & ~(1 << i)) >> i for i in range(len(self.parents))];
            
class Edge ():
    def __init__(self, parent, child):
        self.parent = parent
        self.child = child
        self.ba_edge  = bayes.DirEdge()


class ConditionalProbability():
    @classmethod
    def forChildAndParentVector(cls, child, pv):
        cp = super().when(child = child, parentStates = pv) 
        if not cp:
            cp = cls(child, pv)
        return cp
    @classmethod        
    def forChildAndParentValues(cls, child, *parentStates):
        return cls.forChildAndParentVector(child, child.parentVector(parentStates))
  
    
    def __init__(self, child, probalityTrue, parentVector):
        db.PersistentObject.__init__(self)
        self.parentVector = parentVector
        self.child = child
        self.probTrue = probabilityTrue


        
    
    
        
        
            
